﻿using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using System;
using ExpressionTreeToString;
using System.Linq;
using static System.Linq.Expressions.Expression;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using System.Text;

namespace ConsoleApp3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            //AddBooks();

            //TestExpressionOrDelegate();
            //TestExpressionTreeToString();

            //TestExpressionForDynamicCreate();
            //TestExpressionCreation();

            //QueryBooks("Price", 18.0);
            //QueryBooks("AuthorName", "杨中科");

            QueryBooksPlus<Book>("Price", "AuthorName");
        }

        static Book[] QueryBooks(string title, double? lowerPrice, double? upperPrice, int orderByType)
        {
            using (var ctx = new AppDbContext())
            {
                IQueryable<Book> query = ctx.Books;
                if (!string.IsNullOrWhiteSpace(title))
                {
                    query = query.Where(b => b.Title.Contains(title));
                }
                if (lowerPrice.HasValue)
                {
                    query = query.Where(b => b.Price >= lowerPrice.Value);
                }
                if (upperPrice.HasValue)
                {
                    query = query.Where(b => b.Price <= upperPrice.Value);
                }
                switch (orderByType)
                {
                    case 1: query = query.OrderByDescending(b => b.Price); break;
                    case 2: query = query.OrderBy(b => b.Price); break;
                    case 3: query = query.OrderByDescending(b => b.PubTime); break;
                    case 4: query = query.OrderBy(b => b.PubTime); break;
                }

                return query.ToArray();
            }
        }

        static void QueryBooksPlus<TEntity>(params string[] propNames) where TEntity : class
        {
            ParameterExpression exParameter = Expression.Parameter(typeof(TEntity));
            List<Expression> exProps = new List<Expression>();
            foreach (string propName in propNames)
            {
                Expression exProp = Expression.Convert(
                    Expression.MakeMemberAccess(exParameter, typeof(TEntity).GetProperty(propName)),
                    typeof(object));
                exProps.Add(exProp);
            }

            Expression[] initializers = exProps.ToArray();
            Expression newArrayExp = Expression.NewArrayInit(typeof(object), initializers);
            Expression<Func<TEntity, object[]>> selectExpression = Expression.Lambda<Func<TEntity, object[]>>(newArrayExp, exParameter);

            using (var ctx = new AppDbContext())
            {
                IQueryable<object> selectQueryable = ctx.Set<TEntity>().Select(selectExpression);
                var books = selectQueryable.ToArray();
                foreach (var bArr in books)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (var item in (Array)bArr)
                    {
                        sb.Append(item);
                        sb.Append(", ");
                    }
                    Console.WriteLine(sb.ToString());
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="propName">要查询的属性名字</param>
        /// <param name="value">待比较的值</param>
        /// <returns></returns>
        static IEnumerable<Book> QueryBooks(string propName, object value)
        {
            //Expression<Func<Book, bool>> expr1 = b => b.Price == 5;
            //Console.WriteLine(expr1.ToString("Factory methods", "C#"));

            //Expression<Func<Book, bool>> expr2 = b => b.Title == "零基础趣学C语言";
            //Console.WriteLine(expr2.ToString("Factory methods", "C#"));

            //Type valType = value.GetType();
            Type valType = typeof(Book).GetProperty(propName).PropertyType;

            var b = Parameter(typeof(Book), "b");
            var propAcss = MakeMemberAccess(b, typeof(Book).GetProperty(propName));
            var valCnst = Constant(value);
            Expression body;
            if (valType.IsPrimitive) // 原始类型
            {
                body = Equal(propAcss, valCnst);
            }
            else
            {
                body = MakeBinary(ExpressionType.Equal,
                    propAcss,
                    valCnst, false,
                    typeof(string).GetMethod("op_Equality")
                );
            }

            Expression<Func<Book, bool>> expr;
            expr = Lambda<Func<Book, bool>>(body, b);

            using (var ctx = new AppDbContext())
            {
                return ctx.Books.Where(expr).ToArray();
            }
        }

        static void TestExpressionCreation()
        {
            Expression<Func<Book, bool>> e = b => b.AuthorName.Contains("杨中科") | b.Price > 30;
            Console.WriteLine(e.ToString("Factory methods", "C#"));

            // using static System.Linq.Expressions.Expression;
            var b = Parameter(
                typeof(Book),
                "b"
            );

            var expr = Lambda<Func<Book, bool>>(
                Or(
                    Call(
                        MakeMemberAccess(b,
                            typeof(Book).GetProperty("AuthorName")
                        ),
                        typeof(string).GetMethod("Contains", new[] { typeof(string) }),
                        Constant("杨中科")
                    ),
                    GreaterThan(
                        MakeMemberAccess(b,
                            typeof(Book).GetProperty("Price")
                        ),
                        Constant(30)
                    )
                ),
                b
            );

            using (var ctx = new AppDbContext())
            {
                ctx.Books.Where(expr).ToArray();
            }
        }

        static void TestExpressionForDynamicCreate()
        {
            // Expression<Func<Book, bool>> e1 = b => b.Price > 5;
            var b = Expression.Parameter(typeof(Book), "b");
            var bPrice = Expression.MakeMemberAccess(b, typeof(Book).GetProperty("Price"));
            var const5 = Expression.Constant(5.0, typeof(double));
            var body = Expression.GreaterThan(bPrice, const5);
            var expression = Expression.Lambda<Func<Book, bool>>(body, b);

            using (var ctx = new AppDbContext())
            {
                ctx.Books.Where(expression).ToArray();
            }
        }

        static void TestExpressionTreeToString()
        {
            Expression<Func<Book, bool>> e = b => b.AuthorName.Contains("杨中科") | b.Price > 30;
            Console.WriteLine(e.ToString("Object notation", "C#"));
        }


        static void TestExpressionOrDelegate()
        {
            Expression<Func<Book, bool>> e1 = b => b.Price > 5;
            Expression<Func<Book, Book, double>> e2 = (b1, b2) => b1.Price + b2.Price;

            Func<Book, bool> f1 = b => b.Price > 5;
            Func<Book, Book, double> f2 = (b1, b2) => b1.Price + b2.Price;

            using (var ctx = new AppDbContext())
            {
                ctx.Books.Where(e1).ToArray();
                ctx.Books.Where(f1).ToArray();
            }
        }

        static void AddBooks()
        {
            using (var ctx = new AppDbContext())
            {
                Book book = new Book()
                {
                    Title = "架构整洁之道",
                    AuthorName = "罗伯特.马丁",
                    Price = 128,
                    PubTime = new DateTime(2018, 9, 1)
                };
                ctx.Books.Add(book);
                Book book1 = new Book()
                {
                    Title = "敏捷软件开发（珍藏版）",
                    AuthorName = "罗伯特.马丁",
                    Price = 108,
                    PubTime = new DateTime(2021, 1, 1)
                };
                ctx.Books.Add(book1);
                Book book2 = new Book()
                {
                    Title = "实现领域驱动设计",
                    AuthorName = "沃恩.佛农",
                    Price = 68,
                    PubTime = new DateTime(2014, 3, 1)
                };
                ctx.Books.Add(book2);
                ctx.SaveChanges();
            }
        }
    }
}
